﻿/*
 *   Expression.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Reflection;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.CodeAnalysis;

using BitFox.Evaluator.Functions;

namespace BitFox.Evaluator
{
    /// <summary>
    /// Possible binary and unary operators.
    /// </summary>
    enum Operators
    {
        Nothing,
        OR, AND, NOT, 
        ADD, SUB, MUL, DIV, MOD, NEG,
        EQ, EQS, NE, LE, LT, GE, GT,
    }

    /// <summary>
    /// Class that represents the compiled form of an expression.
    /// </summary>
    public class Expression
    {
        /// <summary>
        /// The alu that performs calculations.
        /// </summary>
        Alu alu;

        /// <summary>
        /// The binary representation of the compiled result.
        /// </summary>
        List<IItem> compiled;

        /// <summary>
        /// The original expression that was compiled into this class.
        /// </summary>
        string src;

        /// <summary>
        /// Helper field with a pointer used during compilation to keep track of 'actual' source character.
        /// </summary>
        int srcPtr;

        /// <summary>
        /// The evaluator to use to solve variable's references. (That, in turn, is the evaluator that create and hold this 'expression')
        /// </summary>
        Evaluator ev;

        /// <summary>
        /// Pointer to the actual item that is executing.
        /// </summary>
        int runpc;
        
        /// <summary>
        /// During execution, the actual stack pointer.
        /// </summary>
        int runsp;

        /// <summary>
        /// The stack to store partial results
        /// </summary>
        object[] rstack = new object[200];

        /// <summary>
        /// Context (containing some settings) that will be used during expression's execution.
        /// </summary>
        public CalculatorContext Context
        {
            get { return ev.Context; }
        }

        /// <summary>
        /// The uncompiled source of this expression.
        /// </summary>
        public string Source
        {
            get { return src; }
        }

        /// <summary>
        /// Get the compiled binary
        /// </summary>
        /// <remarks>
        /// <para>
        /// Do appears that this method is not used at all. But when this code is linked by table's engine, this is used by
        /// rushmore optimization to get the compiled binary and construct the tree.</para>
        /// <para>Any way, I don't want to do this public because I prefer to design a more robust way of serialize a compiled
        /// expression without exposing compiler's internals, like IItem.</para>
        /// </remarks>
        [SuppressMessage("Microsoft.Performance", "CA1811")]
        internal List<IItem> CompiledBinary
        {
            get { return compiled; }
        }

        /// <summary>
        /// Internal constructor using during compilation.
        /// </summary>
        /// <param name="ev"></param>
        internal Expression(Evaluator ev)
        {
            this.ev = ev;
            compiled = new List<IItem>();
            alu = new Alu(ev.Context);
        }

        /// <summary>
        /// Helper methods used during compilation to add a given item to binary output.
        /// </summary>
        /// <param name="item"></param>
        void CompilePush(int item) { compiled.Add(new IntegerItem(item)); }
        void CompilePush(decimal item) { compiled.Add(new DecimalItem(item)); }
        void CompilePush(string item) { compiled.Add(new StringItem(item)); }
        void CompilePush(Operators op) { compiled.Add(new OperatorItem(op)); }
        void CompilePush(bool item) { compiled.Add(new BooleanItem(item)); }
        void CompilePush(int y, int m, int d) { compiled.Add(new DateTimeItem(y, m, d)); }
        void CompilePush(FunctionDescriptor func, int argc, bool isDotted) { compiled.Add(new FuncCallItem(func, argc, isDotted)); }
        void CompilePush(string methName, int argc) { compiled.Add(new FuncCallItem(methName, argc)); }
        void CompilePushName(string name, int arrayIndexCount, bool isDotted)
        {
            compiled.Add(new NameItem(name, arrayIndexCount, isDotted));
        }

        /// <summary>
        /// Helper method used during compilation to force skipping of blank characters from source input.
        /// </summary>
        void SkipBlank()
        {
            while (srcPtr < src.Length && char.IsWhiteSpace(src[srcPtr]))
                srcPtr++;
        }


        /// <summary>
        /// Helper method used during compilation to try the extraction of an operator from source.
        /// </summary>
        /// <param name="monoOper">The detected operator</param>
        /// <param name="monoPrio">The detected the operator's priority</param>
        /// <returns>true if a suitable operator was found in source, else, false.</returns>
        bool CompileOperand(ref Operators monoOper, ref int monoPrio)
        {
            SkipBlank();
            if (srcPtr >= src.Length)
                throw new BitFoxException(ErrorCode.MissingOperand);
            char ch = src[srcPtr];

            if (ch == '!')
            {
                srcPtr++;
                monoOper = Operators.NOT;
                monoPrio = 10;
                return true;
            }

            if (ch == '(')
            {
                srcPtr++;
                CompileSubExpression();
                SkipBlank();
                if (src[srcPtr] != ')')
                    throw new BitFoxException(ErrorCode.MissingToken,"Missing ')'");
                srcPtr++;
                return true;
            }

            if (ch == '.')
                if (CompileBoolean())
                    return true;

            if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')
                if (CompileNumber())
                    return true;

            if (ch == '\"' || ch == '\'')
                return CompileString();

            if (ch == '{')
                return CompileDateTime();

            if (char.IsLetter(ch) || ch == '_' || ch == '.')
                return CompileName();

            if (ch == '-')
            {
                srcPtr++;
                monoOper = Operators.NEG;
                monoPrio = 12;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Helper method used during compilation to try extraction of a boolean constant from the source.
        /// </summary>
        /// <returns>true if a boolean constant was found and extracted from source, else, false</returns>
        /// <remarks>
        /// This method handle following constants:
        ///     .t.  .T., .y., .Y., .s., .N.     = true
        ///     .f.  .F., .n., .N.               = false
        /// </remarks>
        bool CompileBoolean()
        {
            if (srcPtr + 2 >= src.Length) return false;
            if (src[srcPtr + 2] != '.') return false;
            char ch = src[srcPtr + 1];
            srcPtr += 3;
            if (ch == 't' || ch == 'T' || ch == 'y' || ch == 'Y' || ch == 's' || ch == 'S')
            {
                CompilePush(true);
                return true;
            }
            if (ch == 'n' || ch == 'N' || ch == 'f' || ch == 'F')
            {
                CompilePush(false);
                return true;
            }
            srcPtr -= 3;
            return false;
        }

        /// <summary>
        /// Helper method to try extraction of a datetime constant from the source.
        /// </summary>
        /// <returns>true if a datetime constant was found, else, false</returns>
        bool CompileDateTime()
        {
            srcPtr++;
            DateTime cDate = DateTimeFunctions.ParseDateConstant(this.ev.Context, src, ref srcPtr);
            if (cDate == DateTime.MinValue)
                return false;

            if (cDate == DateTime.MaxValue)
                CompilePush(0, 0, 0);               // 'MaxValue' is used to flag EMPTY constants.
            else
                CompilePush(cDate.Year, cDate.Month, cDate.Day);
            srcPtr++;
            return true;
        }

        /// <summary>
        /// Helper method used to force the compilation of a name (as a reference to a var or a call to a function),
        /// from the source.
        /// </summary>
        /// <param name="name">The function's name or the variable's name, already extracted from source.</param>
        /// <param name="delim">The delimiter detected (a '(' or a '[')</param>
        /// <param name="onlyArray">And indicator than, given the context where the name was found, only a 
        /// <param name="isDotted">Indicates than a 'dot' precedes the name, so, the operation applies to a given object</param>
        /// reference to an array is legal.
        /// </param>
        /// <returns>true if the call or the array was found</returns>
        bool CompileArrayOrFunction(string name, char delim, bool onlyArray, bool isDotted)
        {
            int paramCount = 0;
            srcPtr++;
            SkipBlank();
            while (true)
            {
                if (src[srcPtr] == delim) break;

                CompileSubExpression();
                paramCount++;
                SkipBlank();

                if (src[srcPtr] == ',')
                    srcPtr++;
                else
                    if (src[srcPtr] != delim) 
                        throw new BitFoxException(
                            ErrorCode.CompilerError, 
                            string.Format(CultureInfo.InvariantCulture, "',' or '{1}' expected, '{0}' found", src[srcPtr], delim));
            }
            srcPtr++;

            if (onlyArray || (!isDotted && !ev.Functions.ContainsKey(name + paramCount.FoxToString())))
            {
                CompilePushName(name, paramCount, isDotted);
                return true;
            }

            // It's a function call
            if (isDotted)
                CompilePush(name, paramCount);          // Bind at runtime 
            else
            {
                FunctionDescriptor fd = null;
                if(!ev.Functions.TryGetValue(name + paramCount.FoxToString(), out fd))
                    throw new BitFoxException(
                        ErrorCode.CompilerError,
                        string.Format(CultureInfo.InvariantCulture, "Function '{0}' is not recognized as a builtin function", name));
                if (fd.ArgumentCount != paramCount)
                    throw new BitFoxException(
                        ErrorCode.CompilerError,
                        "Invalid number of args for '" + name + "'");
                CompilePush(fd, paramCount, isDotted);
            }
            return true;
        }

        /// <summary>
        /// Helper method that try to extract a name from source and compiling it. If a function call
        /// is found, it is handled by this method too.
        /// </summary>
        /// <returns>true if a name or function call was found, else false</returns>
        bool CompileName()
        {
            bool isDotted = false;

            // If name starts with a DOT, then this is dotted operation of lowest priority
            if (src[srcPtr] == '.')
            {
                isDotted = true;
                srcPtr++;
                SkipBlank();
            }

            int first = srcPtr;
            while (srcPtr < src.Length && (char.IsLetterOrDigit(src[srcPtr]) || src[srcPtr] == '_'))
                srcPtr++;
            string name = src.Substring(first, srcPtr - first).FoxToUpper();

            SkipBlank();
            if (srcPtr < src.Length && src[srcPtr] == '(')
                CompileArrayOrFunction(name, ')', false, isDotted);
            else if(srcPtr < src.Length && src[srcPtr] == '[')
                CompileArrayOrFunction(name, ']', true, isDotted);
            else
                CompilePushName(name, -1, isDotted);
            return true;
        }

        /// <summary>
        /// Helper method that try to extract a number (integer or decimal) from the source.
        /// </summary>
        /// <returns>true if a number was found</returns>
        bool CompileNumber()
        {
            int start = srcPtr;

            // Negative number? (The '-' can represent the inversion operation, rather than a negative number. Something like '-some')
            // If we have a DOT, the we must to check than following characters is a digit, to avoid collision with DOT operation.
            if (src[srcPtr] == '-' || src[srcPtr] == '.')
            {
                char dotDigit = (srcPtr == '.') ? char.MaxValue : '.';
                if ((srcPtr + 1) >= src.Length || !(char.IsDigit(src[srcPtr + 1]) || src[srcPtr + 1] == dotDigit))
                    return false;
            }
            if (src[srcPtr] == '-') srcPtr++;


            // There isn't any problem with numbers starting with a dot, because the DOT operation is in another context.
            bool haveDot = false;
            while (srcPtr < src.Length)
            {
                char ch = src[srcPtr];
                if (!haveDot && ch == '.')
                {
                    haveDot = true;
                    srcPtr++;
                }
                else if (char.IsDigit(ch))
                    srcPtr++;
                else
                    break;
            }

            string temp = src.Substring(start, srcPtr - start);
            if (haveDot)
            {
                decimal dec = decimal.Parse(temp, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
                CompilePush(dec);
            }
            else
                CompilePush(int.Parse(temp, CultureInfo.InvariantCulture));
            return true;
        }

        /// <summary>
        /// Helper method that try to extract a string from the source
        /// </summary>
        /// <returns>true if an string was found, else, false</returns>
        bool CompileString()
        {
            char delim = src[srcPtr++];
            int first = srcPtr;
            while (srcPtr < src.Length && src[srcPtr] != delim) 
                srcPtr++;
            if (src[srcPtr] != delim) 
                throw new BitFoxException(ErrorCode.MissingToken, "Missing " + delim.FoxToString() + " at end of string");
            CompilePush(src.Substring(first, srcPtr - first));
            srcPtr++;
            return true;
        }

        /// <summary>
        /// Compiles a sub expression (one in which the parenthes are NOT used to change priorities)
        /// </summary>
        [SuppressMessage("Microsoft.Maintainability", "CA1502")]
        void CompileSubExpression()
        {
            // Prepares for compilation.
            int[] priorities = new int[100];
            Operators[] operators = new Operators[100];
            int locsp = 0;

            while (srcPtr < src.Length)
            {
                // Get the 'left' part
                Operators op = Operators.Nothing;
                int priority = 0;
                if (!CompileOperand(ref op, ref priority)) break;
                SkipBlank();

                // If previous step has identify an unary operator, get the expression AFTER the operator and put
                // the unary operation in the stack.
                bool salir = false;
                if (op != Operators.Nothing)
                {
                    Operators nope = Operators.Nothing;
                    int nprio = 0;
                    bool resMono = CompileOperand(ref nope, ref nprio);
                    CompilePush(op);
                    if (!resMono) break;
                    op = Operators.Nothing;
                    SkipBlank();
                }

                // Process 'operator' 'right-part' until a breaking char (or EOL) is found in source.
                if (srcPtr >= src.Length) break;
                switch (src[srcPtr++])
                {
                    // The DOT operation is special because the right operand can only be a NAME, ARRAYREF, or FUCCCALL.
                    // To solve this sintax restriction, if we detect a DOT simply don't stack any operand waitig to its
                    // resolucion in CompileName(...)
                    case '.': 
                        op = Operators.Nothing;
                        srcPtr--;                       // Don't skip the dot!!!
                        break;

                    // Common binary operations.
                    case '+': priority = 200; op = Operators.ADD; break;
                    case '-': priority = 200; op = Operators.SUB; break;
                    case '*': priority = 100; op = Operators.MUL; break;
                    case '/': priority = 100; op = Operators.DIV; break;
                    case '%': priority = 80; op = Operators.MOD; break;

                    // Logical OR operator
                    case 'o':
                    case 'O':
                        if ((srcPtr + 1) < src.Length && (src[srcPtr] == 'r' || src[srcPtr] == 'R'))
                            srcPtr++;
                        priority = 600; op = Operators.OR;
                        break;

                    // logical AND operator.
                    case 'a':
                    case 'A':
                        bool ok = false;
                        if ((srcPtr + 1) < src.Length && (src[srcPtr] == 'n' || src[srcPtr] == 'N'))
                        {
                            if ((srcPtr + 2) < src.Length && (src[srcPtr + 1] == 'd' || src[srcPtr + 1] == 'D'))
                            {
                                ok = true;
                                srcPtr += 2;
                                priority = 500;
                                op = Operators.AND;
                            }
                        }
                        if (!ok) salir = true;
                        break;

                    // Another way for logical AND operator (The spanish word for 'AND'  -:)   )
                    case 'y':
                    case 'Y': priority = 500; op = Operators.AND; break;

                    // Operators for comparison.
                    case '#': priority = 400; op = Operators.NE; break;
                    case '=':
                        priority = 400;
                        if ((srcPtr + 1) < src.Length && src[srcPtr] == '=')
                        {
                            srcPtr++;
                            op = Operators.EQS;
                        }
                        else
                            op = Operators.EQ;
                        break;

                    // The 'not equal' operator as used by 'C'
                    case '!':
                        if ((srcPtr + 1) < src.Length && (src[srcPtr] == '='))
                        {
                            srcPtr++;
                            priority = 400;
                            op = Operators.NE;
                        }
                        else
                            throw new BitFoxException(ErrorCode.MissingToken, "Unexpected char after '!'");
                        break;

                    // The 'lower than' / 'lower or equal than' operators
                    case '<':
                        priority = 400;
                        if ((srcPtr + 1) < src.Length && src[srcPtr] == '>')
                        {
                            srcPtr++;
                            op = Operators.NE;
                        }
                        else if ((srcPtr + 1) < src.Length && src[srcPtr] == '=')
                        {
                            srcPtr++;
                            op = Operators.LE;
                        }
                        else
                            op = Operators.LT;
                        break;

                    // The 'higher than' / 'higher or equal than' operators.
                    case '>':
                        priority = 400;
                        if ((srcPtr + 1) < src.Length && src[srcPtr] == '=')
                        {
                            srcPtr++;
                            op = Operators.GE;
                        }
                        else
                            op = Operators.GT;
                        break;

                    // A 'breaking' char - Compilation is stopped
                    default:
                        srcPtr--;
                        salir = true;
                        break;
                }

                // Push 'right-part' to the stack
                if (salir) break;
                if (op != Operators.Nothing)
                {
                    // Send to stack those operations with lower priority than the priority of current operator.
                    while (locsp > 0 && priorities[locsp - 1] <= priority)
                        CompilePush(operators[--locsp]);

                    priorities[locsp] = priority;
                    operators[locsp++] = op;
                }
            }

            // Send to stack those operations not yet stacked.
            while (--locsp >= 0)
                CompilePush(operators[locsp]);
        }

        /// <summary>
        /// Helper method that compile a 'named string'
        /// </summary>
        /// <param name="exp">The source code</param>
        /// <returns>The number of characters consumed from <paramref name="exp"/>.</returns>
        internal int CompileNamedString(string exp)
        {
            srcPtr = 0;
            src = exp;
            SkipBlank();
            if(srcPtr >= src.Length) return 0;

            char ch = src[srcPtr];
            if (ch == '(' || ch == '\"' || ch == '\'')
            {
                srcPtr++;
                try { CompileSubExpression(); }
                catch { compiled.Clear(); throw; }
                SkipBlank();
                if (ch == ')')
                {
                    if (src[srcPtr] != ')') throw new BitFoxException(ErrorCode.SintaxError, "Missing ')'");
                    srcPtr++;
                }
            }
            else
            {
                int startPos = srcPtr;
                while(srcPtr < src.Length)
                    if(char.IsWhiteSpace(src[srcPtr]))
                        break;
                CompilePush(src.Substring(startPos, srcPtr - startPos));
            }

            return srcPtr;
        }

        /// <summary>
        /// Helper method that compiles a given expression.
        /// </summary>
        /// <param name="exp">The expression to compile</param>
        /// <returns>The number of characters consumed from the source expression.</returns>
        internal int Compile(string exp)
        {
            srcPtr = 0;
            src = exp;
            try 
            {
                CompileSubExpression(); 
            }
            catch
            {
                compiled.Clear();
                throw;
            }
            return srcPtr;
        }

        /// <summary>
        /// Get the next compiled item
        /// </summary>
        /// <returns>Thne requested item</returns>
        IItem RunItem()
        {
            if (runpc >= compiled.Count) 
                throw new BitFoxException(ErrorCode.CompilerError, "The compiled expression has errors");
            return compiled[runpc++];
        }

        /// <summary>
        /// Helper method that execute an operation that involves only one item of the stack.
        /// </summary>
        /// <param name="operation">The delegate that executes the requested operation.</param>
        void ExecMonoOperation(Func<object, object> operation)
        {
            if (runsp < 1) throw new BitFoxException(ErrorCode.MissingOperand, "missing operand - one expected");
            object left = rstack[--runsp];
            rstack[runsp++] = operation(left);
        }

        /// <summary>
        /// Helper method that execute an operation that involves two items of the stack.
        /// </summary>
        /// <param name="operation">The delegate that executes the operation.</param>
        void ExecBiOperation(Func<object, object, object> operation)
        {
            if (runsp < 2) 
                throw new BitFoxException(ErrorCode.MissingOperand, "missing operator - two expected.");

            object right = rstack[--runsp];
            object left = rstack[--runsp];
            rstack[runsp++] = operation(left, right);
        }

        /// <summary>
        /// Helper method used by fox engine for executing function calls from inside rushmore optimizer.
        /// </summary>
        /// <param name="ev">The evaluator's context under which to execute the function call</param>
        /// <param name="funcCall">Descriptor of function to call</param>
        /// <param name="argStack">List or arguments for function</param>
        /// <returns>null if the function doesn't return anything, else, the returned value.</returns>
        /// <remarks>
        /// <para>
        /// Do appears that this method is not used at all. But when this code is linked by table's engine, this is used by
        /// rushmore optimization to execute a function call from optimized tree.</para>
        /// <para>Any way, I don't want to do this public because I prefer to design a more robust way of serialize a compiled
        /// expression without exposing compiler's internals, like IItem.</para>
        /// </remarks>
        [SuppressMessage("Microsoft.Performance", "CA1811")]
        static internal object ExecFunctionCall(
            Evaluator ev,
            FuncCallItem funcCall,
            object[] argStack)
        {
            Expression expr = new Expression(ev);
            expr.rstack = argStack;
            expr.runsp = argStack.Length;
            expr.ExecFunctionCall(funcCall);
            if (expr.runsp <= 0) return null;
            if (expr.runsp != 1) throw new BitFoxException(ErrorCode.InternalError);
            return expr.rstack[0];
        }

        /// <summary>
        /// Helper method that performs the call to a function
        /// </summary>
        /// <param name="funcCall">The item, as stored in execution tree, that holds information about the call.</param>
        /// <remarks>
        /// <para>
        /// Do appears that this method is not used at all. But when this code is linked by table's engine, this is used by
        /// rushmore optimization to execute a function call from optimized tree.</para>
        /// <para>Any way, I don't want to do this public because I prefer to design a more robust way of serialize a compiled
        /// expression without exposing compiler's internals, like IItem.</para>
        /// </remarks>
        [SuppressMessage("Microsoft.Performance", "CA1800")]
        internal void ExecFunctionCall(FuncCallItem funcCall)
        {
            int offset = 0;
            MethodInfo mi = null;
            ParameterInfo[] pInfo = null;
            bool addContext = false;
            object target = null;

            if (runsp < funcCall.ArgumentCount)
            {
                throw new BitFoxException(
                    ErrorCode.CannotCallFunction,
                    "Insufficient number of args when running '" + funcCall.Function.Name + "'");
            }
            runsp -= funcCall.ArgumentCount;

            if (funcCall.IsDotted)
            {
                if (runsp < 1)
                    throw new BitFoxException(
                        ErrorCode.CompilerError,
                        "Missing target for . operation on a method");

                target = rstack[--runsp];
                offset = 1;     // We remove the target from rstack, too

                // Search a suitable method
                if (!(target is IResolvable))
                {
                    var candidates = target.GetType().FindMembers(
                        MemberTypes.Method,
                        BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public,
                        (member, name) =>
                        {
                            return (((string)name).FoxCompare(member.Name, true) == 0);
                        },
                        funcCall.MethodName);

                    // Can be more than one candidates but we doesn't support overloads yet
                    if (candidates.Length > 1)
                        throw new BitFoxException(
                            ErrorCode.CannotCallFunction,
                            "Overloads are not supported when a METHOD is resolved");
                    if (candidates.Length < 1)
                        throw new BitFoxException(
                            ErrorCode.CannotCallFunction,
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Cannot find a method named '{0}' in an object of type '{1}'", 
                                funcCall.MethodName, 
                                target.GetType().FullName)
                        );

                    mi = (MethodInfo)candidates[0];
                    pInfo = mi.GetParameters();
                }
            }
            else
            {
                addContext = funcCall.Function.UseContext;
                mi = funcCall.Function.ReflectedMethod;
                pInfo = funcCall.Function.Parameters;
            }

            var args = new List<object>();
            if (mi != null)
            {
                if(addContext)
                    args.Add(this.ev.Context);

                for (int i = 0; i < funcCall.ArgumentCount; i++)
                {
                    object vArg = rstack[runsp + offset + i];
                    if (pInfo.Length > i)
                    {
                        TypeCode targetType = Type.GetTypeCode(pInfo[i].ParameterType);
                        if (targetType == TypeCode.Decimal)
                            vArg = FoxConvert.ToDecimal(vArg);
                        else if (targetType == TypeCode.Int32)
                            vArg = FoxConvert.ToInt32(vArg);
                    }
                    args.Add(vArg);
                }
            }
            else
            {
                for(int i=0; i<funcCall.ArgumentCount; i++)
                    args.Add(rstack[runsp + offset + i]);
            }

            object res = null;
            object[] argsArray = args.ToArray();
            IResolvable iTarget = target as IResolvable;
            if (iTarget != null)
            {
                res = iTarget.SolveMethod(((mi == null) ?
                        funcCall.MethodName :
                        mi.Name),
                    argsArray);
            }
            else
                res = mi.Invoke(target, argsArray);
            rstack[runsp++] = res;
        }

        /// <summary>
        /// Do some automatic conversions between the number expected as parameter and the real number that was supplied.
        /// </summary>
        /// <param name="pInfo"></param>
        /// <param name="args"></param>
        [SuppressMessage("Microsoft.Performance", "CA1822")]
        void MatchArguments(ParameterInfo[] pInfo, object[] args)
        {
            if (args == null)
                throw new BitFoxException(ErrorCode.CannotCallFunction, "Missing arguments for function call");

            for (int i = 0; i < args.Length; i++)
            {
                object vArg = args[i];
                if (pInfo.Length > i)
                {
                    TypeCode target = Type.GetTypeCode(pInfo[i].ParameterType);
                    if (target == TypeCode.Decimal)
                        vArg = FoxConvert.ToDecimal(vArg);
                    else if (target == TypeCode.Int32)
                        vArg = FoxConvert.ToInt32(vArg);
                }
                args[i] = vArg;
            }
        }

        /// <summary>
        /// Helper method that convert all arguments in integers, to be used as indices.
        /// </summary>
        /// <param name="args">The arguments that will be converted</param>
        /// <param name="failIfError">If <b>true</b>, a <see cref="BitFoxException"/> is throw, else, null is returned in
        /// case of error.</param>
        /// <returns>The array's indices</returns>
        /// <exception cref="BitFoxException">ErrorCode.MisssingArrayIndex</exception>
        int[] GetIndices(object[] args, bool failIfError)
        {
            if (args == null)
            {
                if (failIfError)
                    throw new BitFoxException(ErrorCode.MissingArrayIndex, "Missing indices for array operation");
                return null;
            }

            int[] res = new int[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                res[i] = (int)FoxConvert.ToDecimal(args[i]);
                if (!ev.Context.ArrayIndexBaseZero)
                    res[i]--;
            }
            return res;
        }

        /// <summary>
        /// Execute a NAME item
        /// </summary>
        /// <param name="item">The item</param>
        /// <returns>The result of execution</returns>
        object ExecName(NameItem item)
        {
            // Get indexers, if applicable
            object[] indexers = null;
            if (item.IndexersCount >= 0)
            {
                indexers = new object[item.IndexersCount];
                if (runsp < item.IndexersCount)
                    throw new BitFoxException(ErrorCode.CannotCallFunction, "Missing Indexers");
                runsp -= item.IndexersCount;
                for(int i=0; i<item.IndexersCount; i++)
                    indexers[i] = rstack[runsp + i];
            }

            if (item.IsDotted)
            {
                // Get the target of indexing operation.
                if (runsp < 1)
                    throw new BitFoxException(ErrorCode.CannotCallFunction, "Missing operand for DOT operation");

                // If the target implements IResolvable, let the same target to take care of name resolution.
                object target = rstack[--runsp];
                IResolvable irTarget = target as IResolvable;
                if (irTarget != null)
                    return irTarget.SolveVariable(item.Name, indexers);

                // Search a suitable property or indexer using reflection.
                var candidates = target.GetType().FindMembers(
                    MemberTypes.Field | MemberTypes.Property,
                    BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public,
                    (member, _item) =>
                    {
                        NameItem x = (NameItem)_item;
                        return (x.Name.FoxCompare(member.Name, true) == 0);
                    },
                    item);

                // Can be more than one candidates but we doesn't support overloads yet
                if (candidates.Length > 1)
                    throw new BitFoxException(ErrorCode.CannotCallFunction, "Overloads are not supported when a NAME is resolved");
                if (candidates.Length < 1)
                    throw new BitFoxException(
                        ErrorCode.CannotCallFunction,
                        string.Format(
                            CultureInfo.InvariantCulture,
                            "Cannot find a property/field named '{0}' in an object of type '{1}'", 
                            item.Name, 
                            target.GetType().FullName)
                    );

                MemberInfo m = candidates[0];
                if (m.MemberType == MemberTypes.Field)
                {
                    // It's a field. It only can be a single scalar object or an array.
                    FieldInfo fi = (FieldInfo)m;
                    return GetScalarObject(fi.GetValue(target), indexers, item.Name);
                }

                // It's a property. It can be an indexed property or a single property that returns an array or a single property that returns
                // a single object.
                PropertyInfo pi = (PropertyInfo)m;
                ParameterInfo[] parm = pi.GetIndexParameters();
                if (parm.Length > 0)
                {
                    MatchArguments(parm, indexers);
                    return pi.GetValue(target, indexers);
                }
                return GetScalarObject(pi.GetValue(target, null), indexers, item.Name);
            }

            // Scalar variable.
            return ev.Resolve(item.Name, GetIndices(indexers, false));
        }

        /// <summary>
        /// Helper method that get the given element of an array.
        /// </summary>
        /// <param name="res"></param>
        /// <param name="indexers"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        object GetScalarObject(object res, object[] indexers, string name)
        {
            Array rArray = res as Array;
            if (rArray != null)
            {
                int[] indices = GetIndices(indexers, true);
                return rArray.GetValue(indices);
            }

            if (indexers != null && indexers.Length > 0)
            {
                throw new BitFoxException(
                    ErrorCode.CannotCallFunction,
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "You try to handle '{0}' as an ARRAY and it is not an array.", 
                        name));
            }
            return res;
        }

        /// <summary>
        /// This method EXECUTE the expression already compiled.
        /// </summary>
        /// <returns>The result of evaluation.</returns>
        [SuppressMessage("Microsoft.Maintainability", "CA1502")]
        public object Run()
        {
            if (compiled.Count == 0) 
                throw new BitFoxException(ErrorCode.CompilerError, "There were compilation error(s)");

            try
            {
                runpc = runsp = 0;
                while (runpc < compiled.Count)
                {
                    IItem item = RunItem();
                    switch (item.Type)
                    {
                        case EItemType.IntegerNum:
                        case EItemType.DecimalNum:
                        case EItemType.Boolean:
                        case EItemType.DateTime:
                        case EItemType.String:
                            rstack[runsp++] = item.Value;
                            break;

                        case EItemType.Name:
                            object res = ExecName((NameItem)item);
                            rstack[runsp++] = res;
                            break;

                        case EItemType.FuncCall:
                            ExecFunctionCall((FuncCallItem)item);
                            break;

                        case EItemType.Operator:
                            Operators ope = ((OperatorItem)item).Operator;
                            switch (ope)
                            {
                                case Operators.OR:
                                    ExecBiOperation((a, b) => ((bool)a) || ((bool)b));
                                    break;

                                case Operators.AND:
                                    ExecBiOperation((a, b) => ((bool)a) && ((bool)b));
                                    break;

                                case Operators.NOT:
                                    ExecMonoOperation((a) => !((bool)a));
                                    break;

                                case Operators.NE:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) != 0);
                                    break;

                                case Operators.EQ:
                                case Operators.EQS:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) == 0);
                                    break;

                                case Operators.GE:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) >= 0);
                                    break;

                                case Operators.GT:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) > 0);
                                    break;

                                case Operators.LE:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) <= 0);
                                    break;

                                case Operators.LT:
                                    ExecBiOperation((a, b) => alu.Compare(a, b) < 0);
                                    break;

                                case Operators.ADD:
                                    ExecBiOperation((a, b) => alu.Add(a, b));
                                    break;

                                case Operators.SUB:
                                    ExecBiOperation((a, b) => alu.Substract(a, b));
                                    break;

                                case Operators.MUL:
                                    ExecBiOperation((a, b) => alu.Multiply(a, b));
                                    break;

                                case Operators.DIV:
                                    ExecBiOperation((a, b) => alu.Divide(a, b));
                                    break;

                                case Operators.MOD:
                                    ExecBiOperation((a, b) => alu.Modulus(a, b));
                                    break;

                                case Operators.NEG:
                                    ExecMonoOperation((a) => alu.Negate(a));
                                    break;

                                default:
                                    throw new NotSupportedException("Operation not supported: '" + ope.ToString() + "'");
                            }
                            break;
                    }
                }

                if (runsp == 0) throw new BitFoxException(
                    ErrorCode.ExecutionError,
                    "Internal Error - Empty eval stack at return");
                if (runsp != 1) throw new BitFoxException(
                    ErrorCode.ExecutionError,
                    "Internal Error - More than one item in eval stack at return");
                return rstack[0];
            }
            catch (BitFoxException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new BitFoxException(ErrorCode.ExecutionError, e);
            }
        }
    }
}
